import { createLogger, transports, format } from "winston";
import type { Logger } from "winston";
const { combine, timestamp, label, printf } = format;
import { format as formatDate } from "ph-utils/date";

type LoggerLevel = "info" | "error" | "debug" | "warn";
type MessageBase = string | Error | object;
/** 单条日志信息 */
type LogEntry = {
  /** 日志信息 */
  message: MessageBase;
  /** 日志级别 */
  level?: LoggerLevel;
  /** 日志前缀 */
  prefix?: string;
  label?: string;
};

let logger: Logger;

const simpleFormat = printf(({ level, message, label, timestamp }) => {
  return `[${timestamp}] (${label}) [${level.toUpperCase()}]: ${message}`;
});

/**
 * 创建日志记录器
 * @param level 日志记录级别
 * @param loggerLabel label
 */
export function create(level: LoggerLevel = "info", loggerLabel?: string) {
  logger = createLogger({
    format: combine(
      label({ label: loggerLabel || "APP" }),
      timestamp({
        format() {
          return formatDate(undefined, "yyyy-mm-dd HH:MM:ss.S");
        },
      }),
      simpleFormat
    ),
    transports: [new transports.Console()],
    rejectionHandlers: [new transports.Console()],
    level: level,
  });
}

/**
 * 记录日志
 * @param message 日志条目
 */
export function log(message: LogEntry | MessageBase) {
  if (typeof message === "string") {
    logger.info(message);
  } else if (message instanceof Error) {
    logger.error(message.stack);
  } else {
    // 日志内容是 json 对象
    if ((message as LogEntry).message == null) {
      logger.info(JSON.stringify(message));
    } else {
      let logLevel = (message as LogEntry).level || "info";
      let logMsg = (message as LogEntry).message;
      if (logMsg instanceof Error) {
        logMsg = logMsg.stack as string;
        logLevel = "error";
      } else if (typeof logMsg === "object") {
        logMsg = JSON.stringify(logMsg);
      }
      if ((message as LogEntry).prefix) {
        logMsg = `${(message as LogEntry).prefix}: ${logMsg}`;
      }
      logger.log({
        level: logLevel,
        message: logMsg,
        label: (message as LogEntry).label,
      });
    }
  }
}
